﻿#region The MIT License (MIT)
// The MIT License (MIT)
// Copyright (c) 2010 - 2013 Sovacode LLC
// 
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated 
// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to the following conditions:
// 
// The above copyright notice and this permission notice shall be included in all copies or substantial portions
// of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
// BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
#endregion

using System;
using System.ComponentModel;
using System.Linq.Expressions;
using System.Runtime.Serialization;
using System.Xml.Serialization;
using Sovacode.RubiksCube.Common.Attributes;
using Sovacode.RubiksCube.Common.Managers.Validation;

namespace Sovacode.RubiksCube.Common.Base
{
    /// <summary>
    /// Represents an object that can inform about the change of properties and can be validated
    /// </summary>
    [Serializable]
    [DataContract]
    [ValidateByManager]
    public abstract class NotifyValidatedObject : INotifyValidatedObject
    {
        #region Private fields

        /// <summary>
        /// The error message.
        /// </summary>
        [NonSerialized] 
        [XmlIgnore] 
        private string _errorMessage = string.Empty;

        /// <summary>
        /// The validation manager <see cref="ValidatedByManager"/>
        /// </summary>
        [NonSerialized]
        [XmlIgnore] 
        private readonly ValidationManager _validationManager = new ValidationManager();

        /// <summary>
        /// The strategy <see cref="ValidationStrategy"/>
        /// </summary>
        [NonSerialized]
        [XmlIgnore] 
        private ValidationStrategy _strategy = ValidationStrategy.UnSpecified;

        #endregion

        #region INotifyPropertyChanged


        /// <summary>
        /// Occurs when a property value changes.
        /// </summary>
        [field: NonSerialized]
        [field: XmlIgnore]
        [Browsable(false)]
        public event PropertyChangedEventHandler PropertyChanged;

        /// <summary>
        /// Called when property changed.
        /// </summary>
        /// <typeparam name="T">Type</typeparam>
        /// <param name="expression">The array of expression <see cref="Expression{TDelegate}"/> and <see cref="Func{TResult}"/></param>
        protected void OnPropertiesChanged<T>(params Expression<Func<T>>[] expression)
        {
            OnPropertiesChanged(ExpressionHelper.ExpressionToString(expression));
        }

        /// <summary>
        /// Called when property changed.
        /// </summary>
        /// <typeparam name="T">Type</typeparam>
        /// <param name="expression">The expression <see cref="Expression{TDelegate}"/> and <see cref="Func{TResult}"/></param>
        protected void OnPropertyChanged<T>(Expression<Func<T>> expression)
        {
            OnPropertyChanged(expression.ExtractName());
        }


        /// <summary>
        /// Called when property changed.
        /// </summary>
        /// <param name="names">The array of properties</param>
        protected void OnPropertiesChanged(params string[] names)
        {
            foreach (var name in names)
            {
                OnPropertyChanged(name);
            }
        }

        /// <summary>
        /// Called when property changed.
        /// </summary>
        /// <param name="propertyName">Property</param>
        protected void OnPropertyChanged(string propertyName)
        {
            PropertyChangedEventHandler handler = PropertyChanged;

            if (handler == null)
            {
                return;
            }

            handler(this, new PropertyChangedEventArgs(String.Intern(propertyName)));
        }

        #endregion

        #region IDataErrorInfo

        #region Validation Strategy

        /// <summary>
        /// Check for value of <see cref="ValidateByManagerAttribute"/>.
        /// </summary>
        private bool IsManagerPrimaryInValidation
        {
            get
            {
                return Attribute.IsDefined(GetType(), typeof(ValidateByManagerAttribute)) &&
                       ((ValidateByManagerAttribute)Attribute.GetCustomAttribute(GetType(), typeof(ValidateByManagerAttribute)))
                           .ManagerTurnedOn;
            }
        }

        /// <summary>
        /// Gets a value indicating whether the <see cref="IValidationManager"/> is primary in validation of object.
        /// </summary>
        private ValidationStrategy ValidationStrategy
        {
            get
            {
                if (_strategy == ValidationStrategy.UnSpecified)
                {
                    _strategy = IsManagerPrimaryInValidation
                                    ? ValidationStrategy.ValidationManager
                                    : ValidationStrategy.Simple;
                }

                return _strategy;
            }
        }

        /// <summary>
        /// Gets a value indicating whether the <see cref="IValidationManager"/> is primary in validation of object.
        /// </summary>
        protected bool ValidatedByManager
        {
            get { return ValidationStrategy == ValidationStrategy.ValidationManager; }

        }

        #endregion
        
        #region Validation Manager

        /// <summary>
        /// Validation Manager <see cref="ValidationManager"/>
        /// </summary>
        [Browsable(false)]
        public IValidationManager ValidationManager
        {
            get { return _validationManager; }
        }

        /// <summary>
        /// Registration of <see cref="ValidationRule"/> begins here. You should use it in all objects with validation. 
        /// </summary>
        protected virtual void RegisterValidationRules()
        {

        }

        #endregion

        #region Validate

        /// <summary>
        /// Validate property.
        /// </summary>
        /// <typeparam name="T">Type</typeparam>
        /// <param name="expression">Expression <see cref="Expression{TDelegate}"/> and <see cref="Func{TResult}"/></param>
        /// <returns>Empty string in case if property valid</returns>
        protected string Validate<T>(Expression<Func<T>> expression)
        {
            return expression == null
                       ? string.Empty
                       : ((IDataErrorInfo)this)[expression.ExtractName()];
        }

        /// <summary>
        /// Validation of object via <see cref="IDataErrorInfo"/>. You should you use it in all objects with old style validation.
        /// </summary>
        /// <param name="propertyName">Name of property</param>
        /// <returns>Empty string in case if property valid</returns>
        protected virtual string Validate(string propertyName)
        {
            return string.Empty;
        }

        /// <summary>
        /// Validation of object via <see cref="ValidationManager"/>.
        /// </summary>
        /// <param name="validationManager"><see cref="ValidationManager"/></param>
        /// <param name="propertyName">Property Name</param>
        /// <returns>Empty string in case if property valid</returns>
        private string Validate(IValidationManager validationManager, string propertyName)
        {
            if (validationManager == null)
            {
                throw new ArgumentNullException("validationManager");
            }

            return validationManager.Validate(propertyName);
        }

        #endregion

        #region IsValid

        /// <summary>
        /// Validate all properties of object. You should use it in all objects with all style validation
        /// </summary>
        /// <returns>True in case if all properties of object is valid</returns>
        protected virtual bool Valid()
        {
            return true;
        }

        /// <summary>
        /// Run all properties to indicate if this object is Valid. Only when <see cref="ValidatedByManager"/> is true
        /// </summary>
        /// <param name="validationManager"><see cref="ValidationManager"/></param>
        /// <returns>True in case if all properties of object is valid</returns>
        private bool Valid(IValidationManager validationManager)
        {
            if (validationManager == null)
            {
                throw new ArgumentNullException("validationManager");
            }

            return validationManager.IsValid;
        }

        /// <summary>
        /// Gets a value indicating whether the object is valid.
        /// </summary>
        [DisplayName("Is Valid")]
        [Description("Gets a value indicating whether the object is valid")]
        [Category("Basic")]
        [Browsable(false)]
        public bool IsValid
        {
            get { return ValidatedByManager ? Valid(ValidationManager) : Valid(); }
        }

        #endregion

        /// <summary>
        /// Get validation result by index
        /// </summary>
        /// <param name="propertyName">Property</param>
        /// <returns>Empty string in case if property valid</returns>
        [XmlIgnore]
        [Browsable(false)]
        string IDataErrorInfo.this[string propertyName]
        {
            get
            {
                if (ValidatedByManager && ValidationManager.IsEmpty)
                {
                    RegisterValidationRules();
                }

                return ValidatedByManager ? Validate(ValidationManager, String.Intern(propertyName)) : Validate(String.Intern(propertyName));
            }
        }

        /// <summary>
        /// Error information
        /// </summary>
        [XmlIgnore]
        [Browsable(false)]
        public string Error
        {
            get { return _errorMessage; }
            protected set
            {
                if (string.CompareOrdinal(_errorMessage, value) == 0)
                {
                    return;
                }

                _errorMessage = value;

                OnPropertyChanged(() => Error);
            }
        }

        #endregion
    }

    #region Enum

    /// <summary>
    /// Энумератор который указывает, какой метод валидации был приминен к объекту
    /// </summary>
    internal enum ValidationStrategy
    {
        /// <summary>
        /// Если объект валидируется по средствам <see cref="IDataErrorInfo"/> то валидация указывается, как Simple. 
        /// В данном случае следует переопределить некоторые методы для  успешной валидации, к примеру <see cref="NotifyValidatedObject.Validate(string)"/>
        /// </summary>
        Simple,

        /// <summary>
        /// Если валидация объекта происходит посредством <see cref="ValidationManager"/>, то валидация указывается, как Validation Manager.
        /// Для успешной валидации объекта нужно во всех дочерних классах переопределить метод <see cref="NotifyValidatedObject.RegisterValidationRules"/> и
        /// зарегистрировать правила валидации в нем <see cref="ValidationRule"/>.
        /// </summary>
        ValidationManager,

        /// <summary>
        /// Валидация не определена
        /// </summary>
        UnSpecified,
    }

    #endregion
}